/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ricm4.ar.nio.nioEngine;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import ricm4.ar.nio.chat.ChatClient;

/**
 *
 * @author fx
 */
public class NioEngine implements I_NioEngine {

	//common
	private InetAddress hostAddress;
	private int port;
	private Selector selector;
	private ByteBuffer outBuffer;
	private int length;
	private ByteBuffer inBuffer;
	private ByteBuffer lengthBuffer;
	private I_RecvMsgHandler handler;
	
	//for server
	private ServerSocketChannel serverChannel;
	
	//for client
	private SocketChannel socketChannel;
	
	public NioEngine(){
	
	}
	
	@Override
	public void InitializeAsServer(InetAddress hostAddress, int port, I_RecvMsgHandler handler) throws IOException {
		this.hostAddress=hostAddress;
		this.port=port;
		this.handler=handler;
		
		selector=SelectorProvider.provider().openSelector();
		
		serverChannel=ServerSocketChannel.open();
		serverChannel.configureBlocking(false);
		serverChannel.socket().bind(new InetSocketAddress(hostAddress, port));
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
	}

	@Override
	public void InitializeAsClient(InetAddress hostAddress, int port, I_RecvMsgHandler handler) throws IOException {
		this.hostAddress = hostAddress;
		this.port = port;
		this.handler=handler;
		
		selector = SelectorProvider.provider().openSelector();

		socketChannel = SocketChannel.open();
		socketChannel.configureBlocking(false);
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
		socketChannel.connect(new InetSocketAddress(this.hostAddress,this.port));
		
		new ChatClient(this);
	}

	@Override
	public void mainloop() {
		while(true){
			try {
				selector.select();
				Iterator selectedKeys=selector.selectedKeys().iterator();
				while(selectedKeys.hasNext()){
					SelectionKey key=(SelectionKey) selectedKeys.next();
					selectedKeys.remove();
					if(!key.isValid()){
						continue;
					}

					if(key.isAcceptable()){
						handleAccept(key);
					}else if(key.isConnectable()){
						finishConnection(key);
						System.out.println("server connected....");
					}else if(key.isReadable()){
						handleDataIn(key);
					}else if(key.isWritable()){
						handleDataOut(key);
					}
				}
			} catch (IOException ex) {
				System.out.println(ex);
			}
		}
		
	}

	@Override
	public void send(byte[] data) {
		SelectionKey key=socketChannel.keyFor(selector);
		key.interestOps(SelectionKey.OP_WRITE);
		outBuffer=ByteBuffer.wrap(data);
	}

	@Override
	public void send(SocketChannel socketChannel, byte[] data) {
		SelectionKey key=socketChannel.keyFor(selector);
		key.interestOps(SelectionKey.OP_WRITE);
		outBuffer=ByteBuffer.wrap(data);
	}
	
	@Override
	public void terminate() {
		try {
			socketChannel.finishConnect();
		} catch (IOException ex) {
			Logger.getLogger(NioEngine.class.getName()).log(Level.SEVERE, null, ex);
		}
	}
	
	private void finishConnection(SelectionKey key) throws IOException {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		try {
			socketChannel.finishConnect();
		} catch (IOException e) {
			System.out.println(e);
			key.cancel();
			return;
		}
		key.interestOps(SelectionKey.OP_WRITE);
	}
	
	private void handleAccept(SelectionKey key) throws IOException {
		ServerSocketChannel serverSocketChannel=(ServerSocketChannel) key.channel();
		
		SocketChannel socketChannel=serverSocketChannel.accept();
		socketChannel.configureBlocking(false);
		
		socketChannel.register(this.selector, SelectionKey.OP_READ);
	}
	
	private void handleDataIn(SelectionKey key) throws IOException{
		SocketChannel socketChannel = (SocketChannel) key.channel();
		int numRead;
		if(lengthBuffer==null){
			lengthBuffer = ByteBuffer.allocate(Integer.SIZE);
			if(lengthBuffer.remaining()!=Integer.SIZE){
				lengthBuffer.clear();
			}
			//allouer un buffer pour recevoir la longueur du data
			numRead = socketChannel.read(lengthBuffer);
			if (numRead == -1) {
				key.channel().close();
				key.cancel();
				return;
			}
			if(!lengthBuffer.hasRemaining()){
				lengthBuffer.rewind();
				//transformer la longueur recue en un int
				length=lengthBuffer.getInt();
				//allouer un autre buffer pour recevoir le data
				inBuffer=ByteBuffer.allocate(length);
			}
		}
		if(inBuffer!=null){
			try {
				//ne pas mettre read dans une boucle, parce que handler est appelee plusieurs fois pour faire ca
				socketChannel.read(inBuffer);
			} catch (IOException e) {
				key.cancel();
				socketChannel.close();
				return;
			}
		}
		//inBuffer.hasRemaining() teste si le data est recu completement
		if(!inBuffer.hasRemaining()){
			inBuffer.rewind();
			//receivedCB traite le data recu
			handler.receivedCB(inBuffer.array(), socketChannel);
			send(socketChannel, inBuffer.array());
		}
		key.interestOps(SelectionKey.OP_WRITE);
	}
	
	private void handleDataOut(SelectionKey key) throws IOException {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		socketChannel.write(outBuffer); 
		outBuffer.clear();
		key.interestOps(SelectionKey.OP_READ);
	}
}
